#include <log.h>
#include <timer.h>
#include <scutable.h>

int MAIN(void)
{
  log_default_streams_t clg;
  table_t tbl;
  int *dataptr;
  int id, i, j;
  int type;
  tmr_t timer = tmrInit();
  

  clg = logAddDefaultStreams(NULL);
  logEdit(clg.notice, LOG_GROUP_SET_LEVEL);
  
  abortOnError(STREAM(clg.err), tblcreate(&tbl, sizeof(int)));

  lprintf(STREAM(clg.notice), "Testing addition and resizing");
  for (i=0; i < 100; i++)
  {
    abortOnNegative(STREAM(clg.err), id = tbladdnew(&tbl, &dataptr));
    abortOnError(STREAM(clg.err), id != i);
    *dataptr = id;
    
    for (j=0; j <= i; j++)
    {
      abortOnNULL(STREAM(clg.err), tblget(&tbl, j, &dataptr));
      abortOnError(STREAM(clg.err), *dataptr != j);
    }
  }    
  
  lprintf(STREAM(clg.notice), "Testing deletion");
  for (i=0; i < 100; i+= 3)
  {
    abortOnNegative(STREAM(clg.err), tbldel(&tbl, i, 1));

    for (j=0; j < 100; j++)
    {
      if (j <= i && (j%3) == 0)
      {
	abortOnError(STREAM(clg.err), tblget(&tbl, j, NULL) != NULL);
      }
      else
      {
	abortOnNULL(STREAM(clg.err), tblget(&tbl, j, &dataptr));
	abortOnError(STREAM(clg.err), *dataptr != j);
      }	
    }
  }

  lprintf(STREAM(clg.notice), "Testing enumeration");
  for (i=0, id = -1; i < 100; i++)
  {
    if ((i%3)==0) i++;
    id = tblgetnexti(&tbl, id);

    if (i==100)
    {
      abortOnError(STREAM(clg.err), id >= 0);
    }
    else
    {
      abortOnError(STREAM(clg.err), id != i);
    }
  }

  lprintf(STREAM(clg.notice), "Testing index reuse");
  type = -1;

  abortOnNegative(STREAM(clg.err), id = tbladdnew(&tbl, &dataptr));
  *dataptr = 0;
  
  if (id < 100 && type < 0)
  {
    lprintf(STREAM(clg.notice), "Reuse policy is smallest-first");
    type = 0;
  }
  else if (type < 0)
  {
    lprintf(STREAM(clg.notice), "Reuse policy is first-out-first-in");
    type = 1;
  }
  
  if (type == 0)
  {
    for (i=3; i < 100; i+=3)
    {
      abortOnNegative(STREAM(clg.err), id = tbladdnew(&tbl, &dataptr));
      *dataptr = i;
    
      for (j=0; j < 100; j++)
      {
        if (j > i && (j%3) == 0)
        {
      	  abortOnError(STREAM(clg.err), tblget(&tbl, j, NULL) != NULL);
        }
        else
        {
  	  abortOnNULL(STREAM(clg.err), tblget(&tbl, j, &dataptr));
	  abortOnError(STREAM(clg.err), *dataptr != j);
        }	
      }
    }
  }
  else
  {
    for (i=1; i < 28+34; i++)
    {
      abortOnNegative(STREAM(clg.err), id = tbladdnew(&tbl, &dataptr));
      *dataptr = i;

      for (j=100; j < 128; j++)
      {
        if (j > (i+100))
        {
      	  abortOnError(STREAM(clg.err), tblget(&tbl, j, NULL) != NULL);
        }
        else
        {
  	  abortOnNULL(STREAM(clg.err), tblget(&tbl, j, &dataptr));
	  abortOnError(STREAM(clg.err), *dataptr != j-100);
        }	
      }
      
      for (j=0; j < 100; j++)
      {
        if (j > ((i-28)*3) && (j%3)==0)
        {
      	  abortOnError(STREAM(clg.err), tblget(&tbl, j, NULL) != NULL);
        }
        else
        {
  	  abortOnNULL(STREAM(clg.err), tblget(&tbl, j, &dataptr));
  	  if (j%3)
  	  {
  	    abortOnError(STREAM(clg.err), *dataptr != j);
  	  }
  	  else
  	  {
	    abortOnError(STREAM(clg.err), *dataptr != 28 + (j/3));
	  }
        }	
      }
    }
  }
  
  lprintf(STREAM(clg.notice), "Testing hash addition");
  abortOnNegative(STREAM(clg.err), tblcreatehash(&tbl, sizeof(int), 8));

  for (i=0; i < 100; i++)
  {
    abortOnNegative(STREAM(clg.err), tbladdhash(&tbl, &i, i));
    
    for (j=0; j < 100; j++)
    {
      if (j <= i)
      {
	abortOnError(STREAM(clg.err), tblfindhash(&tbl, &j) != j);
      }
      else
      {
	abortOnError(STREAM(clg.err), tblfindhash(&tbl, &j) >= 0);
      }	
    }    
  }

  lprintf(STREAM(clg.notice), "Testing hash deletion");
  for (i=4; i < 100; i+= 3)
  {
    abortOnNegative(STREAM(clg.err), tbldelhash(&tbl, &i));

    for (j=0; j < 100; j++)
    {
      if (j >= 4 && j <= i && ((j-4)%3) == 0)
      {
	abortOnError(STREAM(clg.err), tblfindhash(&tbl, &j) >= 0);
      }
      else
      {
	abortOnError(STREAM(clg.err), tblfindhash(&tbl, &j) != j);
      }	
    }
  }

  lprintf(STREAM(clg.notice), "Testing destruction and error handling");
  tbldestroyhash(&tbl);
  tbldestroy(&tbl, 1);

  abortOnError(STREAM(clg.err), tbldestroy(&tbl, 1) >= 0);
  abortOnError(STREAM(clg.err), tbladd(&tbl, &i) >= 0);
  abortOnError(STREAM(clg.err), tbladdi(&tbl, &i, 0) >= 0);
  abortOnError(STREAM(clg.err), tbladdnew(&tbl, &dataptr) >= 0);
  abortOnError(STREAM(clg.err), tbladdnewi(&tbl, &dataptr, 0) >= 0);
  abortOnError(STREAM(clg.err), tbldel(&tbl, 0, 1) >= 0);
  abortOnError(STREAM(clg.err), tblget(&tbl, 0, NULL) != NULL);
  abortOnError(STREAM(clg.err), tblgetnexti(&tbl, -1) >= 0);

  lprintf(STREAM(clg.notice), "Speed testing...");
  
  abortOnError(STREAM(clg.err), tblcreate(&tbl, sizeof(int)));
  
  tmrStart(&timer);
  for (i=0; i < 100000; i++)
    tbladdnew(&tbl, &dataptr);
  tmrStop(&timer);
  
  lprintf(STREAM(clg.notice), "Insertion (1): %8ld/sec.", (long)(100000000000.0/tmrGetValueInUs(&timer)));
  
  tmrStart(&timer);
  for (i=0; i < 100000; i++)
    tbladdnew(&tbl, &dataptr);
  tmrStop(&timer);
  
  lprintf(STREAM(clg.notice), "Insertion (2); %8ld/sec.", (long)(100000000000.0/tmrGetValueInUs(&timer)));

  tmrStart(&timer);
  for (i=0; i < 100000; i+=2)
    tbldel(&tbl, i, 1);
  tmrStop(&timer);
  
  lprintf(STREAM(clg.notice), "Deletion;      %8ld/sec.", (long)(100000000000.0/tmrGetValueInUs(&timer)));

  tmrStart(&timer);
  for (i=0, id=-1; i < 100000; i++)
    id = tblgetnexti(&tbl, id);
  tmrStop(&timer);
  
  lprintf(STREAM(clg.notice), "Enumeration;   %8ld/sec.", (long)(100000000000.0/tmrGetValueInUs(&timer)));

  return 0;
}
